package org.example.repository;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;

@SuppressWarnings({"rawtypes", "unchecked"})
public class DefaultObjectComparator implements Comparator<Object> {

    private final Comparator comparator;

    public DefaultObjectComparator() {
        comparator = Comparator.nullsLast(new CompositeComparator(
                new ComparatorRoute(new NaturalOrder(),
                        (o1, o2) -> o1 instanceof Comparable)
        ));
    }

    @Override
    public int compare(Object o1, Object o2) {
        return comparator.compare(o1, o2);
    }

    static class CompositeComparator implements Comparator {

        ComparatorRoute[] routes;

        public CompositeComparator(ComparatorRoute... routes) {
            this.routes = routes;
        }

        @Override
        public int compare(Object o1, Object o2) {
            for (ComparatorRoute route : routes) {
                if (route.predicate.apply(o1, o2)) {
                    return route.comparator.compare(o1, o2);
                }
            }
            throw new UnsupportedOperationException("can't compare with object type " + o1.getClass().getName());
        }
    }

    static class ComparatorRoute {
        public BiFunction<Object, Object, Boolean> predicate;
        public Comparator comparator;

        public ComparatorRoute(Comparator comparator, BiFunction<Object, Object, Boolean> predicate) {
            this.comparator = comparator;
            this.predicate = predicate;
        }
    }

    static class NaturalOrder implements Comparator<Comparable> {

        private static final Comparator nature = Comparator.naturalOrder();
        private static final Map<String, Converter> converters = new HashMap<>();

        static {
            converters.put(Long.class.getName(), new ConvertToLong());
            converters.put(Float.class.getName(), new ConvertToFloat());
        }

        @Override
        public int compare(Comparable o1, Comparable o2) {
            Class t1 = o1.getClass();
            Class t2 = o2.getClass();

            if (t1.isAssignableFrom(t2)) {
                return nature.compare(o1, o2);
            }

            Converter converter1 = converters.get(t1.getName());
            if (converter1 != null && converter1.isConvertible(o2)) {
                return nature.compare(o1, converter1.convert(o2));
            }

            Converter converter2 = converters.get(t2.getName());
            if (converter2 != null && converter2.isConvertible(o1)) {
                return nature.compare(converter2.convert(o1), o2);
            }

            throw new UnsupportedOperationException("unable to compare");
        }
    }

    interface Converter {
        boolean isConvertible(Object value);

        Object convert(Object value);
    }

    static class ConvertToLong implements Converter {
        @Override
        public boolean isConvertible(Object value) {
            return value instanceof Integer;
        }

        @Override
        public Object convert(Object value) {
            return ((Integer) value).longValue();
        }
    }

    static class ConvertToFloat implements Converter {
        @Override
        public boolean isConvertible(Object value) {
            return value instanceof Integer;
        }

        @Override
        public Object convert(Object value) {
            return ((Integer) value).floatValue();
        }
    }
}
